﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;

namespace ProgramMain.Google
{
    internal class GoogleTreeLevel<INode> where INode : IGoogleTreeNode
    {
        private GoogleTree<INode> _GoogleTree;
        private int _Level, _GoogleLevel;
        private CoordinateRectangle _Rectangle;

        //массив для хранения дочерних уровней в индекса
        private SortedDictionary<GoogleBlock, GoogleTreeLevel<INode> > _TreeNode;

        #region TreeLevelIndexer class
        internal class TreeLevelIndexer
        {
            private GoogleTreeLevel<INode> _GoogleTreeLevel;

            internal TreeLevelIndexer(GoogleTreeLevel<INode> pGoogleTreeLevel)
            {
                _GoogleTreeLevel = pGoogleTreeLevel;
            }

            public GoogleTreeLevel<INode> this[GoogleBlock block]
            {
                get
                {
                    GoogleTreeLevel<INode> TreeLevel;
                    
                    if (_GoogleTreeLevel._TreeNode == null)
                        _GoogleTreeLevel._TreeNode = new SortedDictionary<GoogleBlock, GoogleTreeLevel<INode> >();
                    
                    if (!_GoogleTreeLevel._TreeNode.TryGetValue(block, out TreeLevel))
                    {
                        //LevelNodeCount - нужен для тюнинга индекса, содержит количество ветвей индекса на каждом уровне
                        if (_GoogleTreeLevel._Level > 0 && _GoogleTreeLevel._Level <= _GoogleTreeLevel._GoogleTree.LevelNodeCount.Length)
                            _GoogleTreeLevel._GoogleTree.LevelNodeCount[_GoogleTreeLevel._Level - 1]++;

                        TreeLevel = new GoogleTreeLevel<INode>(
                            _GoogleTreeLevel._GoogleTree, 
                            _GoogleTreeLevel._Level + 1,
                            _GoogleTreeLevel.NextGoogleLevel,
                            (GoogleRectangle)block);
                        _GoogleTreeLevel._TreeNode.Add(block, TreeLevel);
                    }
                    return TreeLevel;
                }
            }

            public int Length
            {
                get 
                {
                    return _GoogleTreeLevel._TreeNode != null ? _GoogleTreeLevel._TreeNode.Count : 0;
                }
            }

            public void Remove(GoogleBlock block)
            {
                if (_GoogleTreeLevel._TreeNode != null)
                {
                    //LevelNodeCount - нужен для тюнинга индекса, содержит количество ветвей индекса на каждом уровне
                    _GoogleTreeLevel._GoogleTree.LevelNodeCount[_GoogleTreeLevel._Level - 1]--; 
                    _GoogleTreeLevel._TreeNode.Remove(block);
                }
            }
        }
        #endregion
        public readonly TreeLevelIndexer TreeNode;

        //массив для храрения элементов (поддерживаются точки, линии и прямоугольники)
        private SortedDictionary<int, INode > _Content;
        private SortedDictionary<int, INode > Content
        {
            get { return _Content ?? (_Content = new SortedDictionary<int, INode >()); }
        }
        public int ContentLength
        {
            get
            {
                return _Content != null ? _Content.Count : 0;
            }
        }

        public bool HasChildContent
        {
            get
            {
                return TreeNode.Length > 0 || ContentLength > 0;
            }
        }

        public enum ActionType {Insert, Delete};

        public GoogleTreeLevel(GoogleTree<INode> pGoogleTree, int pLevel, int pGoogleLevel, CoordinateRectangle pRectangle)
        {
            _GoogleTree = pGoogleTree;
            _Level = pLevel;
            _GoogleLevel = pGoogleLevel;
            _Rectangle = pRectangle;

            TreeNode = new TreeLevelIndexer(this);
        }

        private int GoogleNumLevel(int Level)
        {
            return (int)MapUtilities.NumLevel((int)_GoogleTree.LevelPower[Level]);
        }

        private int GoogleLevelAddon()
        {
            return GoogleNumLevel(_Level) - 1;
        }

        private int NextGoogleLevel
        {
            get { return _GoogleLevel + GoogleLevelAddon(); }
        }

        internal void Action(INode Node, ActionType actionType)
        {
            //вставка элемента в индекс, или его удаление из индекса
            if (_Level < _GoogleTree.LevelPower.Length)
            {
                if (Node.NodeType == GoogleTreeTypes.Point)
                    PointLevelAction(Node, actionType);
                else if (Node.NodeType == GoogleTreeTypes.Line)
                    LineLevelAction(Node, actionType);
                else if (Node.NodeType == GoogleTreeTypes.Rectangle)
                    RectangleLevelAction(Node, actionType);
            }
            else
            {
                //если дошли до нижнего уровня, то добавить/удалить элемент
                lock (this)
                {
                    if (actionType == ActionType.Insert)
                    {
                        Content[Node.RowID] = Node;
                    }
                    else if (actionType == ActionType.Delete)
                    {
                        Content.Remove(Node.RowID);
                    }
                }
            }
        }

        private void PostLevelAction(GoogleBlock block, GoogleTreeLevel<INode> level, ActionType actionType)
        {
            switch (actionType)
            {
                case ActionType.Delete:
                    {
                        //при удалении объектов удалить текущюю ветку идекса, если она пустая
                        if (!level.HasChildContent)
                            TreeNode.Remove(block);
                    } break;
            }
        }

        private void PointLevelAction(INode Node, ActionType actionType)
        {
            GoogleBlock block = Node.Coordinate.GetGoogleBlock(NextGoogleLevel);
            lock (this)
            {
                //поиск для точки дочернего элемента в индексе
                GoogleTreeLevel<INode> level = TreeNode[block];
                
                level.Action(Node, actionType);

                PostLevelAction(block, level, actionType);
            }
        }

        private void LineLevelAction(INode Node, ActionType actionType)
        {
            int BlockViewLevel = NextGoogleLevel;
            
            CoordinateRectangle line = Node.Rectangle;
            Rectangle rect = new GoogleRectangle(line, BlockViewLevel).GetGoogleBlockView();

            int deltaX = (rect.Left <= rect.Right) ? 1 : -1;
            int deltaY = (rect.Top <= rect.Bottom) ? 1 : -1;

            //поиск для линии дочерних элементов в индексе(линия может входить в несколько элементов индекса)
            for (int X = rect.Left; (deltaX == 1 && X <= rect.Right) || (deltaX == -1 && X >= rect.Right); X += deltaX)
            {
                for (int Y = rect.Top; (deltaY == 1 && Y <= rect.Bottom) || (deltaY == -1 && Y >= rect.Bottom); Y += deltaY)
                {
                    GoogleBlock block = new GoogleBlock(X, Y, BlockViewLevel);
                    GoogleRectangle googleRect = (GoogleRectangle)block;
                    
                    //проверка вхождения линии в каждый из потенциальных дочерних элементов индекса
                    if (googleRect.LineContains(line) != InterseptResult.None)
                    {
                        lock (this)
                        {
                            GoogleTreeLevel<INode> level = TreeNode[block];

                            level.Action(Node, actionType);

                            PostLevelAction(block, level, actionType);
                        }
                    }
                }
            }
        }

        private void RectangleLevelAction(INode Node, ActionType actionType)
        {
            int BlockViewLevel = NextGoogleLevel;

            Rectangle rect = new GoogleRectangle(Node.Rectangle, BlockViewLevel).GetGoogleBlockView();

            int deltaX = (rect.Left <= rect.Right) ? 1 : -1;
            int deltaY = (rect.Top <= rect.Bottom) ? 1 : -1;

            //поиск для прямоугольника дочерних элементов в индексе(прямоугольник может входить в несколько элементов индекса)
            for (int X = rect.Left; (deltaX == 1 && X <= rect.Right) || (deltaX == -1 && X >= rect.Right); X += deltaX)
            {
                for (int Y = rect.Top; (deltaY == 1 && Y <= rect.Bottom) || (deltaY == -1 && Y >= rect.Bottom); Y += deltaY)
                {
                    GoogleBlock block = new GoogleBlock(X, Y, BlockViewLevel);
                    lock (this)
                    {
                        GoogleTreeLevel<INode> level = TreeNode[block];

                        level.Action(Node, actionType);

                        PostLevelAction(block, level, actionType);
                    }
                }
            }
        }

        public HashSet<IGoogleTreeNode> Query(CoordinateRectangle rectangle, InterseptResult ParentResult)
        {
            //запрос на поиск элементов в индексе в заданном квадрате координат
            HashSet<IGoogleTreeNode> hashSet = new HashSet<IGoogleTreeNode>();
            lock (this)
            {
                if (_Level < _GoogleTree.LevelPower.Length)
                {
                    if (_TreeNode != null)
                    {
                        foreach (GoogleTreeLevel<INode> TreeLevel in _TreeNode.Values)
                        {
                            InterseptResult res = (ParentResult == InterseptResult.Supersets)
                                ? InterseptResult.Supersets : TreeLevel._Rectangle.RectangleContains(rectangle);
                            if (res != InterseptResult.None)
                            {
                                hashSet.UnionWith(TreeLevel.Query(rectangle, res));
                            }
                            if (res == InterseptResult.Contains) break;
                        }
                    }
                }
                else
                {
                    if (_Content != null)
                    {
                        foreach (INode Node in _Content.Values)
                        {
                            InterseptResult res = (ParentResult == InterseptResult.Supersets) ? InterseptResult.Supersets : InterseptResult.None;
                            if (res != InterseptResult.Supersets)
                            {
                                //проверка на вхождение текущего элемента в индексе в заданном квадрате координат
                                if (Node.NodeType == GoogleTreeTypes.Point)
                                    res = rectangle.PointContains(Node.Coordinate);
                                else if (Node.NodeType == GoogleTreeTypes.Line)
                                    res = rectangle.LineContains(Node.Rectangle);
                                else if (Node.NodeType == GoogleTreeTypes.Rectangle)
                                    res = rectangle.RectangleContains(Node.Rectangle);
                            }
                            if (res != InterseptResult.None)
                            {
                                hashSet.Add(Node as IGoogleTreeNode);
                            }
                        }
                    }
                }
            }
            return hashSet;
        }

        public HashSet<IGoogleTreeNode> Distance(Coordinate coordinate, double variance)
        {
            //запрос на поиск элементов в индексе удаленных на определенное растояние (в метрах) от заданной точки
            HashSet<IGoogleTreeNode> hashSet = new HashSet<IGoogleTreeNode>();
            lock (this)
            {
                if (_Level < _GoogleTree.LevelPower.Length)
                {
                    if (_TreeNode != null)
                    {
                        foreach (GoogleTreeLevel<INode> TreeLevel in _TreeNode.Values)
                        {
                            double res = TreeLevel._Rectangle.RectangeDistance(coordinate);
                            if (res <= variance)
                            {
                                hashSet.UnionWith(TreeLevel.Distance(coordinate, variance));
                            }
                        }
                    }
                }
                else
                {
                    if (_Content != null)
                    {
                        foreach (INode Node in _Content.Values)
                        {
                            //расчет растояния для текущего элемента (в метрах) от заданной точки
                            double res = 0;
                            if (Node.NodeType == GoogleTreeTypes.Point)
                                res = Node.Coordinate.Distance(coordinate);
                            else if (Node.NodeType == GoogleTreeTypes.Line)
                                res = Node.Rectangle.LineDistance(coordinate);
                            else if (Node.NodeType == GoogleTreeTypes.Rectangle)
                                res = Node.Rectangle.RectangeDistance(coordinate);
                            if (res < variance)
                            {
                                hashSet.Add(Node as IGoogleTreeNode);
                            }
                        }
                    }
                }
            }
            return hashSet;
        }

        public void Clear()
        {
            lock (this)
            {
                if (_Level < _GoogleTree.LevelPower.Length)
                {
                    if (_TreeNode != null)
                    {
                        foreach (GoogleTreeLevel<INode> TreeLevel in _TreeNode.Values)
                        {
                            TreeLevel.Clear();
                        }
                        _TreeNode = null;
                    }
                }
                else
                {
                    if (_Content != null)
                    {
                        _Content.Clear();
                        _Content = null;
                    }
                }
            }
        }
    }

    public class GoogleTree<INode> where INode : IGoogleTreeNode
    {
        private LevelPowerTypes[] _LevelPower = new LevelPowerTypes[4];

        #region LevelPowerIndexer class
        public class LevelPowerIndexer
        {
            private GoogleTree<INode> _GoogleTree;

            internal LevelPowerIndexer(GoogleTree<INode> pGoogleTree)
            {
                _GoogleTree = pGoogleTree;
            }
            public LevelPowerTypes this[int Level] 
            {
                get
                {
                    if (Level > 0 && Level <= _GoogleTree._LevelPower.Length)
                        return _GoogleTree._LevelPower[Level - 1];
                    else
                        return LevelPowerTypes.None;
                }
                set
                {
                    if (_GoogleTree.ItemCount > 0) return;

                    if (Level > 0 && Level <= _GoogleTree._LevelPower.Length)
                        _GoogleTree._LevelPower[Level - 1] = value;
                }
            }
            public int Length
            {
                get
                {
                    return _GoogleTree._LevelPower.Length + 1;
                }
            }
        }
        #endregion
        public readonly LevelPowerIndexer LevelPower;

        public int ItemCount { get; private set; }

        internal int[] LevelNodeCount = null;
        
        //ссылка на главный уровень в индексе
        //по умолчанию индекс содержит 4 уровня, не включая главного, но при желании может быть увеличен
        private GoogleTreeLevel<INode> _RootLevel;

        public GoogleTree()
        {
            ItemCount = 0;
            LevelPower = new LevelPowerIndexer(this);

            SetLevels(
                LevelPowerTypes.Medium,
                LevelPowerTypes.Medium,
                LevelPowerTypes.Medium,
                LevelPowerTypes.Medium);
        }

        public GoogleTree(LevelPowerTypes Level1, LevelPowerTypes Level2, LevelPowerTypes Level3, LevelPowerTypes Level4)
        {
            ItemCount = 0;
            LevelPower = new LevelPowerIndexer(this);

            SetLevels(Level1, Level2, Level3, Level4);
        }

        protected void SetLevels(LevelPowerTypes Level1, LevelPowerTypes Level2, LevelPowerTypes Level3, LevelPowerTypes Level4)
        {
            if (ItemCount > 0) return;

            LevelPower[1] = Level1;
            if (LevelPower.Length > 2) LevelPower[2] = Level2;
            if (LevelPower.Length > 3) LevelPower[3] = Level3;
            if (LevelPower.Length > 4) LevelPower[4] = Level4;

            for (int i = 5; i < LevelPower.Length; i++)
            {
                LevelPower[i] = LevelPowerTypes.Low;
            }
            _RootLevel = new GoogleTreeLevel<INode>(this, 1, 1, CoordinateRectangle.Empty);

            //LevelNodeCount - нужен для тюнинга индекса, содержит количество ветвей индекса на каждом уровне
            LevelNodeCount = new int[_LevelPower.Length];
        }

        protected void Insert(INode Value)
        {
            lock (this)
            {
                ItemCount++;
            }

            _RootLevel.Action(Value, GoogleTreeLevel<INode>.ActionType.Insert);
        }

        protected void Delete(INode Value)
        {
            lock (this)
            {
                ItemCount--;
            }

            _RootLevel.Action(Value, GoogleTreeLevel<INode>.ActionType.Delete);
        }

        public HashSet<IGoogleTreeNode> Query(CoordinateRectangle rectangle)
        {
            return _RootLevel.Query(rectangle, InterseptResult.None);
        }

        public HashSet<IGoogleTreeNode> Distance(Coordinate coordinate, double variance)
        {
            return _RootLevel.Distance(coordinate, variance);
        }

        public void Clear()
        {
            ItemCount = 0;
            _RootLevel.Clear();
            //LevelNodeCount - нужен для тюнинга индекса, содержит количество ветвей индекса на каждом уровне
            LevelNodeCount = new int[_LevelPower.Length];
        }
    }
}
